<!DOCTYPE html>
<html>
  <head>
    <title>
      Handle Silent Inputs to AnalyserNode
    </title>
    <script src="../../resources/testharness.js"></script>
    <script src="../../resources/testharnessreport.js"></script>
    <script src="../resources/audit-util.js"></script>
    <script src="../resources/audit.js"></script>
  </head>
  <body>
    <script id="layout-test-code">
      let audit = Audit.createTaskRunner();
      let sampleRate = 16000;
      let renderDuration = 1;
      let renderFrames = renderDuration * sampleRate;

      audit.define(
          {label: 'connected', description: 'Test handling of silent inputs'},
          function(task, should) {
            tester(should, false, '0').then(task.done.bind(task));
          });

      audit.define(
          {label: 'auto-pull', description: 'Test handling of silent inputs'},
          function(task, should) {
            tester(should, true, '1').then(task.done.bind(task));
          });

      audit.define(
          {
            label: 'timing',
            description: 'Test shifting in of zeroes after source has stopped'
          },
          function(task, should) {
            let renderQuantumFrames = 128;

            // sampleRate chosen to be a power of two so we don't have round-off
            // errors in computing the times for when to suspend the context.
            let context = new OfflineAudioContext(1, 16384, 16384);
            let source = new ConstantSourceNode(context);

            // The fftSize for the analyser is fairly arbitrary, except the code
            // assumes it is larger than 128.
            let analyser = new AnalyserNode(context, {fftSize: 2048});

            source.connect(analyser).connect(context.destination);

            source.start();

            // Stop the source after 1 fftSize frames.
            let time = analyser.fftSize / context.sampleRate;
            source.stop(time);

            // Verify that the time data at this point is constant.
            context.suspend(time)
                .then(() => {
                  let data = new Float32Array(analyser.fftSize);
                  analyser.getFloatTimeDomainData(data);
                  should(
                      data,
                      'At time ' + context.currentTime +
                          ' Analyser frames [0, ' + analyser.fftSize + ')')
                      .beConstantValueOf(1);
                })
                .then(context.resume.bind(context));

            // After each rendering quantum from the point at which the source
            // stopped, verify that zeroes are inserted into the time data one
            // rendering quantum at a time.

            let limit = analyser.fftSize / renderQuantumFrames;

            for (let k = 1; k <= limit; ++k) {
              let analyserTime = (analyser.fftSize + k * renderQuantumFrames) /
                  context.sampleRate;
              context.suspend(analyserTime)
                  .then(() => {
                    let data = new Float32Array(analyser.fftSize);
                    let indexNewest =
                        analyser.fftSize - k * renderQuantumFrames;
                    analyser.getFloatTimeDomainData(data);
                    if (k < limit) {
                      should(
                          data.slice(0, indexNewest),
                          'At time ' + context.currentTime +
                              ' Analyser frames [0, ' + indexNewest + ')')
                          .beConstantValueOf(1);
                    }
                    should(
                        data.slice(indexNewest),
                        'At time ' + context.currentTime +
                            ' Analyser frames [' + indexNewest + ', ' +
                            analyser.fftSize + ')')
                        .beConstantValueOf(0);
                  })
                  .then(context.resume.bind(context));
            }

            // Start the test
            context.startRendering().then(() => task.done());
          });

      audit.run();

      function tester(should, isAutoPullTest, prefix) {
        // Connect an oscillator to an analyser for testing the time data of the
        // analyser after the oscillator stops.
        let context = new OfflineAudioContext(1, renderFrames, sampleRate);
        let source = new OscillatorNode(context);
        let analyser = new AnalyserNode(context, {fftSize: 128});
        let timeData = new Float32Array(analyser.fftSize);
        timeData.fill(NaN);

        source.connect(analyser);

        // For the automatic pull test, leave the analyser output disconnected.
        if (isAutoPullTest) {
          source.connect(context.destination);
        } else {
          analyser.connect(context.destination);
        }

        source.start();

        // Stop the source well in advance of when we want to get the time data
        // from the analyser.
        let stopTime = 0.1;
        let dataTime = 0.5;

        source.stop(stopTime);
        context.suspend(dataTime)
            .then(() => {
              analyser.getFloatTimeDomainData(timeData);
            })
            .then(context.resume.bind(context));

        return context.startRendering().then(buffer => {
          should(timeData, prefix + ': Analyser time data at time ' + dataTime)
              .beConstantValueOf(0);
        });
      }
    </script>
  </body>
</html>
